import OSS from 'ali-oss'
import apis from '@/plugins/http'
import modal from './modal'

const oss = {
    ossClient: null, // oss客户端实例
    expiration: '', // 过期时间
    ossUrl: '', // oss url路径地址前缀

    partSize: 2 * 1024 * 1024, // 每个分片大小(byte)
    parallel: 5, // 同时上传的分片数
    pointerCache: { // 所有分片上传文件的检查点，储存在localStorage，中断下次可以断点续传
        get(file, name) {
            const cache = localStorage.getItem('uploadCacheKey')
            if (!cache || cache === 'null') return null
            const cacheJson = JSON.parse(cache)
            if (cacheJson.name !== name) return null
            cacheJson.file = file
            return cacheJson
        },
        set(pointer) {
            localStorage.setItem('uploadCacheKey', JSON.stringify(pointer))
        },
        remove() {
            localStorage.removeItem('uploadCacheKey')
        }
    },

    // 获取 STS 凭证，创建 OSS Client
    async initOSSClient() {
        // 获取STS Token
        const res = await apis.getOssToken()
        if (res && res.code === 200) {
            // 获取过期时间
            this.expiration = res.data.expiration
            // 拼接url，后面有用到
            this.ossUrl = `https://${ res.data.bucket }.${ res.data.region }.aliyuncs.com/`
            this.ossClient = new OSS(res.data)
        } else {
            modal.msgError('获取OSS STS临时授权失败')
        }
    },

    formatCurrentTime(type) {
        const currentDate = new Date()
        const year = String(currentDate.getFullYear())
        const shortYear = year.slice(2)
        const month = String(currentDate.getMonth() + 1).padStart(2, '0')
        const day = String(currentDate.getDate()).padStart(2, '0')
        const hours = String(currentDate.getHours()).padStart(2, '0')
        const minutes = String(currentDate.getMinutes()).padStart(2, '0')
        const seconds = String(currentDate.getSeconds()).padStart(2, '0')
        // 格式化为 "YYYYMMDD-hhmmss"
        if (type === 'YYYYMMDD-hhmmss') {
            return `${ year }${ month }${ day }-${ hours }${ minutes }${ seconds }`
        }// 格式化为 "-YYMMDDhhmmss"
        else if (type === '-YYMMDDhhmmss') return `-${ shortYear }${ month }${ day }${ hours }${ minutes }${ seconds }`
    },

    // 随机生成一个文件名
    getFileName(file) {
        const {name} = file
        if (name) {
            return this.formatCurrentTime('YYYYMMDD-hhmmss') + '-' + parseInt(Math.random() * 100000) + name.substr(name.lastIndexOf('.'))
        } else {
            return this.formatCurrentTime('YYYYMMDD-hhmmss') + '-' + parseInt(Math.random() * 100000) + '.png'
        }
    },

    // 获取文件内容MD5名称，放置重复上传，但是不同用户上传相同图片或者不同使用地方上传相同图片，一被删除就会出问题了
    // 注意使用了该方法生成文件名称，后端一定要使用判断数据库重复文件的操作，不能直接删除以及修改
    // base64转Blob对象
    async dataUrl2BlobObj(dataurl) {
        return new Promise((resolve, reject) => {
            const arr = dataurl.split(',')
            const mime = arr[0].match(/:(.*?);/)[1]
            const bstr = atob(arr[1])
            let n = bstr.length
            const u8arr = new Uint8Array(n)
            while (n--) {
                u8arr[n] = bstr.charCodeAt(n)
            }
            const blob = new Blob([u8arr], {type: mime})
            resolve(blob)
        })
    },
    // 将base64转换为文件
    dataUrl2File(dataurl) {
        const arr = dataurl.split(',')
        const mime = arr[0].match(/:(.*?);/)[1]
        const bstr = atob(arr[1])
        let n = bstr.length
        const u8arr = new Uint8Array(n)
        while (n--) {
            u8arr[n] = bstr.charCodeAt(n)
        }
        return new File([u8arr], this.fileInfo.name, {
            type: mime
        })
    },
    // blobUrl转Blob对象
    async blobUrl2BlobObj(url) {
        return new Promise((resolve, reject) => {
            const xhr = new XMLHttpRequest()
            xhr.open('GET', url, true)
            xhr.responseType = 'blob'
            xhr.onload = function (e) {
                if (this.status === 200) {
                    const Blob = this.response
                    resolve(Blob)
                }
            }
            xhr.send()
        })
    },

    /**
     * 上传至OSS
     * @param file
     * @param path
     * @param {Boolean} publicRead  是否设置为公共读
     * @param {Object} watchUploadInfo  由调用组件传进来，是为了监听进度情况，从而改变调用组件中相应的值
     *                                    比如改变调用组件中进度条的值，这里必须是对象，否则调用组件中值不会改变，可为空
     * @param {Boolean} multipart 大于2MB文件上传是否分片上传， 默认分片，如果为false的话，则普通上传
     * @param onProgress 上传进度回调函数，参数为进度值，0-100，可为空
     */
    async upload(file, path, publicRead = true, watchUploadInfo = null, multipart = true, onProgress = null) {
        let fileObj = null
        if (typeof (file) === 'string' && file.indexOf('base64') >= 0) {
            // 如果是base64，转为Blob对象
            fileObj = await this.dataUrl2BlobObj(file)
        } else if (typeof (file) === 'string' && file.indexOf('blob') >= 0) {
            // 如果是blobUrl，转为Blob对象
            fileObj = await this.blobUrl2BlobObj(file)
        } else {
            fileObj = file
        }

        // 如果文件大学小于分片大小，使用普通上传，否则使用分片上传
        if (fileObj.size < this.partSize) {
            return await this.commonUpload(fileObj, path, publicRead, watchUploadInfo, onProgress)
        } else if (!multipart) {
            return await this.commonUpload(fileObj, path, publicRead, watchUploadInfo, onProgress)
        } else {
            return await this.multipartUpload(fileObj, path, publicRead, watchUploadInfo, onProgress)
        }
    },

    // 普通上传
    async commonUpload(file, path, publicRead, watchUploadInfo, onProgress) {
        if (!this.ossClient) {
            await this.initOSSClient()
        }

        if (this.expiration && this.isExpiringSoon(this.expiration)) {
            console.log(`STS token 将在 1 分钟内过期，将暂停上传，重新获得STS token后继续上传`)
            if (this.ossClient) {
                this.ossClient.cancel()
            }
            await this.initOSSClient()
        }
        try {
            const fileName = this.getFileName(file)
            // 向OSS上传头像
            const result = await this.ossClient.put(path + fileName, file)
            // 设置该文件的访问权限为公共读 (私有:private,公共读:public-read,公共读写:public-read-write)
            if (publicRead) {
                await this.ossClient.putACL(path + fileName, 'public-read')
                if (watchUploadInfo && this.isVideoFile(fileName)) {
                    // 如果是视频文件，在watchUploadInfo中返回视频第1s的视频截帧的图片
                    watchUploadInfo.imgSrc = this.ossUrl + result.name + '?x-oss-process=video/snapshot,t_1000,f_jpg,w_715,h_500,m_fast'
                    // 若是视频上传，一般用watchUploadInfo储存数据，所以需要这边赋值
                    watchUploadInfo.url = this.ossUrl + result.name
                }
            } else {
                // 如果不是公共读，上传的视频文件，则watchUploadInfo里有第一帧图片，需要上传至oss
                if (watchUploadInfo && this.isVideoFile(fileName)) {
                    // 上传
                    if (watchUploadInfo.imgSrc) watchUploadInfo.imgSrc = await this.upload(watchUploadInfo.imgSrc, path)
                    // 若是视频上传，一般用watchUploadInfo储存数据，所以需要这边赋值
                    watchUploadInfo.url = this.ossUrl + result.name
                }
            }

            if (result.res.statusCode === 200) {
                if (watchUploadInfo) { watchUploadInfo.percent = 100 }
                if (onProgress) { onProgress(100) }
                console.log(result.url, '123---url')
                return result.url
            } else {
                console.log(result.url, 'shibai---url')
                modal.msgError('上传失败')
            }
        } catch (e) {
            console.log(e)
            modal.msgError('上传错误')
        }
        return null
    },

    // 分片上传，断点续传，返回进度条信息
    async multipartUpload(file, path, publicRead, watchUploadInfo, onProgress) {
        if (!this.ossClient) {
            await this.initOSSClient()
        }

        if (this.expiration && this.isExpiringSoon(this.expiration)) {
            console.log(`STS token 将在 1 分钟内过期，将暂停上传，重新获得STS token后继续上传`)
            if (this.ossClient) {
                this.ossClient.cancel()
            }
            await this.initOSSClient()
        }

        try {
            const fileName = this.getFileName(file)
            let checkpoint = this.pointerCache.get(file, path + fileName) // 断点续传指针

            // 失败后尝试两次
            for (let i = 0 ; i < 2 ; i++) {
                try {
                    const that = this
                    const result = await this.ossClient.multipartUpload(path + fileName, file, {
                        parallel: that.parallel,
                        partSize: that.partSize,
                        checkpoint,
                        async progress(percentage, cpt) { // 上传进度
                            // 判断STS Token是否将要过期，过期则重新获取
                            await that.ifExpiration(file, path, watchUploadInfo)
                            // console.log(parseInt(percentage * 100, 10))
                            if (watchUploadInfo) { watchUploadInfo.percent = parseInt(percentage * 100, 10) }
                            if (onProgress) {onProgress(parseInt(percentage * 100 + '', 10))}
                            that.pointerCache.set(cpt)
                            checkpoint = cpt
                        }
                    })
                    // 设置该文件的访问权限为公共读 (私有:private,公共读:public-read,公共读写:public-read-write)
                    if (publicRead) {
                        await this.ossClient.putACL(path + fileName, 'public-read')
                        if (watchUploadInfo && this.isVideoFile(fileName)) {
                            // 如果是视频文件，在watchUploadInfo中返回视频第1s的视频截帧的图片
                            watchUploadInfo.imgSrc = this.ossUrl + result.name + '?x-oss-process=video/snapshot,t_1000,f_jpg,w_715,h_500,m_fast'
                            // 若是视频上传，一般用watchUploadInfo储存数据，所以需要这边赋值
                            watchUploadInfo.url = this.ossUrl + result.name
                        }
                    } else {
                        // 如果不是公共读，上传的视频文件，则watchUploadInfo里有第一帧图片，需要上传至oss
                        if (watchUploadInfo && this.isVideoFile(fileName)) {
                            // 上传
                            if (watchUploadInfo.imgSrc) watchUploadInfo.imgSrc = await this.upload(watchUploadInfo.imgSrc, path)
                            // 若是视频上传，一般用watchUploadInfo储存数据，所以需要这边赋值
                            watchUploadInfo.url = this.ossUrl + result.name
                        }
                    }
                    this.pointerCache.remove()
                    // console.log('上传成功：', result)
                    // 返回上传路径
                    return this.ossUrl + result.name
                } catch (e) {
                   modal.msgError('上传失败，请检查网络或重新上传')
                }
            }
        } catch (e) {
            modal.msgError('上传错误')
        }
        return null
    },

    // 判断STS Token是否将要过期，过期则重新获取
    async ifExpiration(file, path, watchUploadInfo) {
        if (this.expiration && this.isExpiringSoon(this.expiration)) {
            console.log(`STS token 将在 1 分钟内过期，将暂停上传，重新获得STS token后继续上传`)
            if (this.ossClient) {
                this.ossClient.cancel()
            }
            await this.initOSSClient()
            await this.multipartUpload(file, path, watchUploadInfo)
        }
    },

    // 判断是否是视频文件
    isVideoFile(fileName) {
        const videoType = [
            'avi',
            'wmv',
            'mkv',
            'mp4',
            'mov',
            'rm',
            '3gp',
            'flv',
            'mpg',
            'rmvb'
        ]
        return RegExp('\\.(' + videoType.join('|') + ')$', 'i').test(fileName.toLowerCase())
    },

    // 暂停上传
    stop() {
        if (this.ossClient) this.ossClient.cancel()
    },

    // 续传
    resume(file, path, watchUploadInfo) {
        if (this.ossClient) {
            this.upload(file, path, watchUploadInfo)
        }
    },

    isExpiringSoon(expirationTime) {
        const currentTime = new Date()
        const expirationDate = new Date(expirationTime)

        // 计算当前时间和过期时间之间的时间差（以毫秒为单位）
        const timeDifference = expirationDate.getTime() - currentTime.getTime()

        // 将时间差转换为分钟
        const minutesDifference = timeDifference / (1000 * 60)

        // 判断是否不足一分钟
        return minutesDifference <= 1
    },

    // 删除单个文件，参数是包不包含url都可以
    async delete(fileUrl) {
        if (!this.ossClient) {
            await this.initOSSClient()
        }
        try {
            const result = await this.ossClient.delete(fileUrl.replace(this.ossUrl, ''))
            if (result.res.statusCode === 200) {
                console.log('删除成功')
            } else {
                modal.msgError('删除失败')
            }
        } catch (e) {
           modal.msgError('删除错误')
        }
    },

    // 批量删除objects
    async deleteMulti(fileObjects) {
        if (!this.ossClient) {
            await this.initOSSClient()
        }

        try {
            const result = await this.ossClient.deleteMulti(fileObjects, {
                quiet: true // 通过quiet参数来指定是否返回删除的结果
            })
            if (result.res.statusCode === 200) {
                console.log('删除成功')
            } else {
                modal.msgError('删除失败')
            }
        } catch (e) {
           modal.msgError('删除错误')
        }
    },

    /**
     * 对请求资源Url签名，使得私有资源可临时访问
     * @param {String} resourse 资源路径
     * @param {Object} options 配置签名参数，如过期失效
     */
    async signatureUrl(resourse, options) {
        if (!this.ossClient) {
            await this.initOSSClient()
        }

        if (this.expiration && this.isExpiringSoon(this.expiration)) {
            console.log(`STS token 将在 1 分钟内过期，将暂停上传，重新获得STS token后继续上传`)
            if (this.ossClient) {
                this.ossClient.cancel()
            }
            await this.initOSSClient()
        }
        // 调用sdk中封装好的方法进行签名
        return this.ossClient.signatureUrl(resourse.replace(this.ossUrl, ''), options)
    },

    /**
     * 对请求资源 请求头中增加 Authorization 签名 再请求
     * 此方法就是生成 在HTTP请求中增加 Authorization 包含签名（Signature）的Header信息
     * @param {Object} resourse
     */
    async authorization(resourse) {
        if (!this.ossClient) {
            await this.initOSSClient()
        }

        if (this.expiration && this.isExpiringSoon(this.expiration)) {
            console.log(`STS token 将在 1 分钟内过期，将暂停上传，重新获得STS token后继续上传`)
            if (this.ossClient) {
                this.ossClient.cancel()
            }
            await this.initOSSClient()
        }
        const res = this.ossClient.createRequest({
            method: 'get',
            bucket: this.ossClient.options.bucket,
            object: resourse.replace(this.ossUrl, '')
        })
        // 返回的信息是这样的格式：{url: url,params: reqParams}
        return res
    }
}

export default oss
